#include <string>
#include <vector>
#include "Level.h"

#include "MyGame.h"
#include "Factory.h"
#include "SoundCache.h"
#include "MusicCache.h"
#include "SurfaceCache.h"
#include "Physics.h"
#include "Camera.h"
#include "NumberUtility.h"

/* These are the collections of object definitions expected at the end of the level file */
enum ObjectTypes {  evNotDefined,
                    evHero,
                    evSprite,
                    evTrigger
                 };

// Map to associate the strings with the enum values
static std::map<std::string, ObjectTypes> s_mapObjectTypes;

Level::Level()
{
    this->type = stLevel;
    this->fileName = "Untitled.map";
    this->levelName = "Untitled level";
    this->author = "Unknown author";
    this->description = "No level description available";
    this->decryptor = NULL;
    this->player = NULL;
    this->shouldResetSpawn = false;
};

Level::Level(string fileName, string password)
{
    this->type = stLevel;
    this->levelName = "Untitled level";
    this->author = "Unknown author";
    this->description = "No level description available";
    this->decryptor = new Decryptor(password.c_str());
    this->fileName = fileName;
    this->player = NULL;
    this->LoadLevel(fileName);
    this->shouldResetSpawn = false;
};

Level::~Level()
{
    if (this->decryptor)
    {
        delete this->decryptor;
        this->decryptor = NULL;
    }
    vector< BaseUnit* >::iterator I;
    delete player;
    player = NULL;
    for (I = friendlies.begin(); I < friendlies.end(); ++I)
    {
        delete (*I);
    }
    friendlies.clear();
    for (I = enemies.begin(); I < enemies.end(); ++I)
    {
        delete (*I);
    }
    enemies.clear();
    for (I = triggers.begin(); I < triggers.end(); ++I)
    {
        delete (*I);
    }
    triggers.clear();
    list< BackgroundImage* >::iterator L;
    for (L = backgroundImages.begin(); L != backgroundImages.end(); ++L)
    {
        delete (*L);
    }
    backgroundImages.clear();
    list< StatefulUnit* >::iterator M;
    for (M = effects.begin(); M != effects.end(); ++M)
    {
        delete (*M);
    }
    effects.clear();
    delete tileMap;
    if (not pushed)
    {
        SurfaceCache::GetSurfaceCache()->Clear();
        SoundCache::GetSoundCache()->Clear();
        MusicCache::GetMusicCache()->Clear();
    }
    if (isPaused)
        EFFECTS->removeOverlay();
	list<SpawnUnitData*>::iterator N;
	for (N = queue.begin(); N != queue.end(); ++N)
	{
		delete (*N);
	}
    queue.clear();
#ifdef _DEBUG
    cout << "Deleted old level" << endl;
#endif
};

/* preload the map to allow us to effectively switch on a string */
void Level::init()
{
    if (s_mapObjectTypes.size() == 0)
    {
        s_mapObjectTypes["Hero"] = evHero;
        s_mapObjectTypes["Sprites"] = evSprite;
        s_mapObjectTypes["Triggers"] = evTrigger;
    }
    GFX::setClearColour(BLACK);

    // pause screen
    pauseText.loadFont("font/Freebooter.ttf",144);
    pauseText.setColour(WHITE);
    pauseText.setPosition(235,10);
    pauseMenu.setMenuStart(0,150);
    pauseMenu.loadFont("font/Freebooter.ttf", 42);
    pauseMenu.setTextColour(WHITE);
    pauseMenu.setTextSelectionColour(RED);
    pauseMenu.getTextObject()->setBoundaries(Vector2di(0,0),Vector2di(GFX::getXResolution(),GFX::getYResolution()));
    pauseMenu.getTextObject()->setAlignment(CENTRED);
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("Resume");
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("Quit");
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("--- Controls: ---");
    pauseMenu.setIsSelectable(false);
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("X,A - jump");
    pauseMenu.setIsSelectable(false);
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("B,Y - attack, throw enemy (hold)");
    pauseMenu.setIsSelectable(false);
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("DOWN - duck");
    pauseMenu.setIsSelectable(false);
    pauseMenu.addItem(MENU_STRING_ITEM);
    pauseMenu.setMenuItemText("UP - enter doors");
    pauseMenu.setIsSelectable(false);

#ifdef _DEBUG
    debugPrint.loadFont("font/unispace.ttf",12);
    debugPrint.setPosition(10,10);
    debugPrint.setColour(GREEN);
    debugPrint.setRelativity(true);

    pix.setColour(YELLOW);
    pix.setAlpha(50);
#endif
}

/* set, or reset the password for decrypting encrypted level files */
void Level::SetPassword(string password)
{
    if (this->decryptor)
    {
        delete this->decryptor;
    }
    this->decryptor = new Decryptor(password.c_str());
}

/* Read a level file and parse it */
void Level::LoadLevel(string fileName)
{
    wstring temp;
    //  Remove windows line endings...
    fileName = StringUtility::stripLineEndings(fileName);
    /* Check if the file exists, if it does not, look for an encrypted file of the same name with .enc appended */
    this->fileName = fileName;
    if (!this->FileExists(fileName))
    {
        fileName += ".enc";
    }
#if _DEBUG
    cout << "Trying to open: " << fileName << endl;
#endif

    /* Either decrypt the file or read it straight, ending with the data in the wstring temp either way */
    if (fileName.length()>4 && fileName.substr(fileName.length()-4,4) == ".enc")
    {
        temp = this->decryptor->decrypt_w((char *)fileName.c_str());
    }
    else
    {
        /* read the entire file, then convert from UTF-8 to wstring */
        string line, temp2;
        ifstream file(fileName.c_str());
        while(std::getline(file, line))
        {
            temp2 += StringUtility::stripLineEndings(line) + "\n";
        }
        file.close();
        temp = this->decryptor->stringToWString(temp2);
    }

    LoadLevelFromWString(temp);
}

/* Parse a levelfile from a wstring */
void Level::LoadLevelFromWString(wstring temp)
{
    unsigned int currentPos = 0;
    wstring cur;
    string stemp;
    string type;

    getLine(temp, cur, currentPos);
    currentPos = 0;
    stemp = decryptor->wstringToString(cur);
#ifdef _DEBUG
    cout << "Loading level..." << endl;
#endif
    if (stemp.substr(0,1) == "#" || stemp.substr(0,1) == "v" || stemp.substr(0,1) == "V")
    {
        LoadLevelFromWStringVersioned(temp);
    }
    else
    {
        cout << "Critical Error: Bad level file!" << endl;
        exit(-1);
    }
};

/* Find the next line in a wstring */
bool Level::getLine(wstring temp, wstring& line, uint& currentPos)
{
    int nextPos = temp.find(L"\n", currentPos);
    if (nextPos==-1)
    {
        currentPos=nextPos;
        line = L"";
        return false;
    }
    else
    {
        wstring result = temp.substr(currentPos, (nextPos-currentPos));
        currentPos = nextPos+1;
        line = result;
        return true;
    }
}

/* Parse a levelfile from a wstring, new format */
void Level::LoadLevelFromWStringVersioned(wstring temp)
{
    // TODO: Get rid of all unnecessary global (in the context of this function)
    // variables and create them locally when needed

    // TODO: Make use of map and switch statements for faster loading (s_mapObjectTypes)

    Factory *f = Factory::GetFactory();

    uint currentPos = 0;
    uint nextPos = 0;
    uint i = 0;
    wstring wline;
    string line;
    vector<string> split;
    string field;
    wstring wcur;
    string cur;
    string stemp;

    string tileMapFileName;
    int transparent=-1;
    uint tileSize;
    uint tileMapWidth;
    uint tileMapHeight;
    wstring foregroundLayer;
    wstring middleLayer;
    wstring backgroundLayer;
    wstring propertiesLayer;
    uint mapWidth;
    uint mapHeight;

    Vector2di backgroundSize = Vector2di(0,0);
    Vector2di backgroundOffset = Vector2di(0,0);
    Vector2di backgroundMoveA = Vector2di(1,1);
    Vector2di backgroundForEveryB = Vector2di(1,1);
    pair<bool,bool> backgroundRepeat = make_pair(false,false);
    uint backgroundStartIndex = 0;
    uint backgroundNumFrames = 1;
    uint backgroundFrameRate = 0;
    string backgroundFileName;
    uint backgroundSheetWidth;
    uint backgroundSheetHeight;
    AnimatedSprite *backgroundSprite;
    uint backgroundTransparentColour = -1;
    bool backgroundUseTransparentPoint = false;
    Vector2di backgroundTransparentPoint = Vector2di(0,0);
    BackgroundImage *backgroundImageTemp;
    SDL_Surface *surface;
    SoundCache *sounds = SoundCache::GetSoundCache();
    MusicCache *music = MusicCache::GetMusicCache();
    SurfaceCache *surfaces = SurfaceCache::GetSurfaceCache();

    string version;

    while (getLine(temp, wline, currentPos)==true)
    {
        line = decryptor->wstringToString(wline);

        line = StringUtility::substrReplace(line,"\r","");
        // If it is not a comment line
        if (line.size() > 0 and line.substr(0,1) != "#" and line.at(0) > 13)
        {
            // chop on equal into at most two parts
            tokenize(line,split,"=",2);
            field = split[0];
            if (split.size()<=1)
            {
                //apparently there is nothing after the equal
                //check for layers
                if (field == "ForegroundLayer")
                {
                    foregroundLayer = L"";
                    for (i=0; i<mapHeight; ++i)
                    {
                        nextPos = temp.find(L"\n", currentPos);
                        /* here we actually want the newline character */
                        wcur = temp.substr(currentPos, nextPos-currentPos+1);
                        currentPos = nextPos+1;
                        foregroundLayer += wcur;
                    }
                }
                else if (field == "MiddleLayer")
                {
                    middleLayer = L"";
                    for (i=0; i<mapHeight; ++i)
                    {
                        nextPos = temp.find(L"\n", currentPos);
                        /* here we actually want the newline character */
                        wcur = temp.substr(currentPos, nextPos-currentPos+1);
                        currentPos = nextPos+1;
                        middleLayer += wcur;
                    }
                }
                else if (field == "BackgroundLayer")
                {
                    backgroundLayer = L"";
                    for (i=0; i<mapHeight; ++i)
                    {
                        nextPos = temp.find(L"\n", currentPos);
                        /* here we actually want the newline character */
                        wcur = temp.substr(currentPos, nextPos-currentPos+1);
                        currentPos = nextPos+1;
                        backgroundLayer += wcur;
                    }
                }
                else if (field == "PropertiesLayer")
                {
                    propertiesLayer = L"";
                    for (i=0; i<mapHeight; ++i)
                    {
                        nextPos = temp.find(L"\n", currentPos);
                        /* here we actually want the newline character */
                        wcur = temp.substr(currentPos, nextPos-currentPos+1);
                        currentPos = nextPos+1;
                        propertiesLayer += wcur;
                    }
                }
            }
            else
            {
                cur = split[1];
                if (field == "Version")
                {
                    version = cur;
                }
                else if (field == "LevelName")
                {
                    levelName = cur;
                }
                else if (field == "Author")
                {
                    author = cur;
                }
                else if (field == "Description")
                {
                    description = cur;
                }
                else if (field == "TileMap")
                {
                    tileMapFileName = cur;
                }
                else if (field == "TransparentKey")
                {
                    transparent = atoi(cur.c_str());;
                }
                else if (field == "TransparentPoint")
                {
//                    levelName = split[1];
                }
                else if (field == "TileSize")
                {
                    tileSize = atoi(cur.c_str());
                }
                else if (field == "TileMapWidth")
                {
                    tileMapWidth = atoi(cur.c_str());
                }
                else if (field == "TileMapHeight")
                {
                    tileMapHeight = atoi(cur.c_str());
                }
                else if (field == "TileMapSize")
                {
                    tokenize(cur,split,",",2);
                    tileMapWidth = atoi(split[0].c_str());
                    tileMapHeight = atoi(split[1].c_str());
                }
                else if (field == "LevelWidth")
                {
                    mapWidth = atoi(cur.c_str());
                }
                else if (field == "LevelHeight")
                {
                    mapHeight = atoi(cur.c_str());
                }
                else if (field == "LevelSize")
                {
                    tokenize(cur,split,",",2);
                    mapWidth = atoi(split[0].c_str());
                    mapHeight = atoi(split[1].c_str());
                }
                else if (field == "Hero")
                {
                    this->player = (Hero*)(f->GetHero(this, cur, tileSize));
                }
                else if (field == "Sprite")
                {
                    AddSpawnUnit(f->GetSprite(cur, tileSize));
                }
                else if (field == "Trigger")
                {
                    SpawnUnit(f->GetTrigger(cur, tileSize));
                }
                else if (field == "BackgroundImageWidth")
                {
                    backgroundSize.x = atoi(cur.c_str());
                }
                else if (field == "BackgroundImageHeight")
                {
                    backgroundSize.y = atoi(cur.c_str());
                }
                else if (field == "BackgroundImageSize")
                {
                    tokenize(cur,split,",",2);
                    backgroundSize.x = atoi(split[0].c_str());
                    backgroundSize.y = atoi(split[1].c_str());
                }
                else if (field == "BackgroundImageOffset")
                {
                    tokenize(cur,split,",",2);
                    backgroundOffset.x = atoi(split[0].c_str());
                    backgroundOffset.y = atoi(split[1].c_str());
                }
                else if (field == "BackgroundImageMoveA")
                {
                    tokenize(cur,split,",",2);
                    backgroundMoveA.x = atoi(split[0].c_str());
                    backgroundMoveA.y = atoi(split[1].c_str());
                }
                else if (field == "BackgroundImageForEveryB")
                {
                    tokenize(cur,split,",",2);
                    backgroundForEveryB.x = atoi(split[0].c_str());
                    backgroundForEveryB.y = atoi(split[1].c_str());
                }
                else if (field == "BackgroundImageTransparentKey")
                {
                    backgroundTransparentColour = atoi(split[1].c_str());
                    backgroundUseTransparentPoint = false;
                }
                else if (field == "BackgroundImageTransparentPoint")
                {
                    tokenize(cur,split,",",2);
                    backgroundTransparentPoint = Vector2di(atoi(split[0].c_str()),atoi(split[1].c_str()));
                    backgroundUseTransparentPoint = true;
                }
                else if (field == "BackgroundImageRepeatX")
                {
                    backgroundRepeat.first = StringUtility::stringToBool(split[1]);
                }
                else if (field == "BackgroundImageRepeatY")
                {
                    backgroundRepeat.second = StringUtility::stringToBool(split[1]);
                }
                else if (field == "BackgroundImageRepeat")
                {
                    tokenize(cur,split,",",2);
                    backgroundRepeat.first = StringUtility::stringToBool(split[0]);
                    backgroundRepeat.second = StringUtility::stringToBool(split[1]);
                }
                else if (field == "BackgroundImage")
                {
                    tokenize(cur,split,",",6);
                    if (split.size() > 3)
                    {
                        backgroundFrameRate = atoi(split[1].c_str());
                        backgroundStartIndex = atoi(split[2].c_str());
                        backgroundNumFrames = atoi(split[3].c_str());
                    }
                    else
                    {
                        backgroundStartIndex = 0;
                        backgroundNumFrames = 1;
                        backgroundFrameRate = 0;
                    }
                    backgroundFileName = split[0];
                    bool dontNeedReturn;
                    surface = surfaces->GetSurface(backgroundFileName,dontNeedReturn);
                    if (!surface)
                    {
                        cout << "Critical error: reading " << backgroundFileName << " failed!" << endl;
                        exit(-1);
                    }

                    // Figure out the number of xTiles and yTiles
                    backgroundSheetWidth = surface->w/backgroundSize.x;
                    backgroundSheetHeight = surface->h/backgroundSize.y;

                    backgroundSprite = new AnimatedSprite();
                    backgroundSprite->loadFrames(surface,backgroundSheetWidth,backgroundSheetHeight,backgroundStartIndex,backgroundNumFrames);
                    if (backgroundUseTransparentPoint)
                    {
                        backgroundSprite->setTransparentColour(backgroundTransparentPoint);
                    }
                    else
                    {
                        if (backgroundTransparentColour >= 0)
                        {
                            backgroundSprite->setTransparentColour(Colour(backgroundTransparentColour));
                        }
                    }

                    if (backgroundFrameRate > 0)
                    {
                        backgroundSprite->setFrameRate((TimerScalers)backgroundFrameRate);
                    }

                    backgroundImageTemp = new BackgroundImage(backgroundSprite, backgroundOffset, backgroundMoveA, backgroundForEveryB,backgroundRepeat);
                    this->backgroundImages.push_back(backgroundImageTemp);
                }
                else if (field == "Sound")
                {
                    tokenize(cur,split,",",3);

                    if (split.size() == 3)
                    {
                        sounds->AddSound(split[0],split[1],(bool)atoi(split[2].c_str()));
                    }
                    else if (split.size() == 2)
                    {
                        if (split[2].length() > 1)
                            sounds->AddSound(split[0],split[1],false);
                        else
                            sounds->AddSound(split[0],(bool)atoi(split[2].c_str()));
                    }
                    else if (split.size() == 1)
                    {
                        sounds->AddSound(split[0],false);
                    }
                }
                else if (field == "Music")
                {
                    tokenize(cur,split,",",2);
                    if (split.size() == 2)
                    {
                        music->AddMusic(split[0],split[1]);
                    }
                    else if (split.size() == 1)
                    {
                        music->AddMusic(split[0]);
                    }
                }
                else if (field == "BackgroundMusic")
                {
                    music->PlayMusic(cur);
                }
            }
        }
    }
    #ifdef _DEBUG
    list<SpawnUnitData*>::iterator I;
    cout << "List of units:" << endl;
    cout << "Classname,spawnPosX,Y,Parameter,Respawn" << endl;
    for (I = queue.begin(); I != queue.end(); ++I)
    {
        cout << (*I)->classname << "," << (*I)->spawnPos.x << " | " << (*I)->spawnPos.y << "," << (*I)->parameter << "," << StringUtility::boolToString((*I)->respawn,true) << endl;
    }
    #endif

    tileMap = new MapData;
    if (middleLayer.size() > 0)
        tileMap->assign(foregroundLayer, middleLayer, backgroundLayer, propertiesLayer, tileMapFileName, transparent, tileMapWidth, tileMapHeight);
    else
        tileMap->assign(foregroundLayer, backgroundLayer, propertiesLayer, tileMapFileName, transparent, tileMapWidth, tileMapHeight);
}

/* Utility function to test for file existence */
//TODO: move utility functions to singleton utility class
bool Level::FileExists(string filename)
{
    struct stat stFileInfo;
    bool blnReturn;
    int intStat;

    // Attempt to get the file attributes
    intStat = stat(filename.c_str(),&stFileInfo);
    if(intStat == 0)
    {
        // We were able to get the file attributes
        // so the file obviously exists.
        blnReturn = true;
    }
    else
    {
        // We were not able to get the file attributes.
        // This may mean that we don't have permission to
        // access the folder which contains this file.
        blnReturn = false;
    }

    return(blnReturn);
}

bool Level::VecCompare(const Vector2df& v1, const Vector2df& v2)
{
    if (v1.x + v1.y <= v2.x + v2.y)
        return true;
    return false;
}

bool Level::IsInRect(const Vector2df& vec, const pair<Vector2df,Vector2df>& rect)
{
    if (vec.x >= rect.first.x && vec.x <= rect.second.x && vec.y >= rect.first.y && vec.y <= rect.second.y)
        return true;
    return false;
}

Vector2df Level::GetCenterPosition(Vector2df Pos, Vector2df Size)
{
    return Vector2df(Pos.x+Size.x/2,Pos.y+Size.y/2);
}

int Level::calculateJumpHeight(CRuint speed)
{
    int result = 0;
    for (int I=speed; I > 0; I-=PHYSICS->getGravity())
    {
        result += I;
    }
    return result;
}

/* This function sets up the map and state - must be called before */
/* the state becomes active (i.e. before ->Render() gets called.   */
/* Generally, this will be called automatically by MyGame.         */
void Level::Activate()
{
    nullify = false;

    CAMERA->centerOnPlayer(GetHero());

}

void Level::userInput()
{
    input->update();

#ifdef PLATFORM_PC
    if(input->isQuit())
        nullifyState();
#endif
    if(input->isStart())
    {
        pauseToggle();
        input->resetKeys();
    }

    if (player->thinkTimeFinished())
    {
        player->handleInput(input);
    }
}

void Level::pauseInput()
{
    input->update();

#ifdef PLATFORM_PC
    if(input->isQuit())
        nullifyState();
#endif

    if(input->isStart())
    {
        pauseToggle();
    }
    if (input->isUp())
        pauseMenu.menuUp();
    else if (input->isDown())
        pauseMenu.menuDown();
    if (input->isA())
    {
        switch (pauseMenu.getSelection())
        {
        case 0: // Resume
            pauseToggle();
            break;
        case 1: // quit
            setNextState(STATE_MAIN);
            break;
        }
    }
    input->resetKeys();
}

void Level::render()
{
    GFX::clearScreen();

    list<BackgroundImage*>::iterator K;
    for (K = backgroundImages.begin(); K != backgroundImages.end(); ++K)
    {
        (*K)->updateScreenPosition(CAMERA->getMapOffset());
        (*K)->render();
    }

    tileMap->render(BG,CAMERA->getMapOffset());
    tileMap->render(MID,CAMERA->getMapOffset());

#ifdef _DEBUG
    pix.setColour(MAGENTA);
    pix.setAlpha(255);
#endif

    vector< BaseUnit* >::iterator I;
    for (I = enemies.begin(); I < enemies.end();++I)
    {
        (*I)->updateScreenPosition(CAMERA->getMapOffset());
        (*I)->render();
    }

    for (I = friendlies.begin(); I < friendlies.end();++I)
    {
        (*I)->updateScreenPosition(CAMERA->getMapOffset());
        (*I)->render();
    }

    if (GetHero())
    {
        GetHero()->updateScreenPosition(CAMERA->getMapOffset());
        GetHero()->render();
    }

    // Render trigger (the ifdefs have been moved inside the actual trigger to allow for triggers with graphics ie a push-trigger displaying the direction of the push)
    for (I = triggers.begin(); I < triggers.end(); ++I)
    {
        (*I)->updateScreenPosition(CAMERA->getMapOffset());
        (*I)->render();
    }

    StatefulUnit *effect;
    list<StatefulUnit *>::iterator iter;
    for (iter = effects.begin(); iter != effects.end();++iter)
    {
        effect = *iter;
        effect->updateScreenPosition(CAMERA->getMapOffset());
        effect->render();
    }

    tileMap->render(FG,CAMERA->getMapOffset());
#ifdef _DEBUG
    tileMap->render(PROP,CAMERA->getMapOffset());
#endif

    EFFECTS->render();

#ifdef _DEBUG
    pix.setColour(YELLOW);
    pix.setAlpha(50);

    uint tileSize = GetTileSize();
    for (int I = 0; I < GetHero()->m_collisionInfo.getSizeX(); ++I)
    {
        Vector2df temp = GetHero()->m_collisionInfo.getTileX(I);
        temp *= Vector2df(tileSize,tileSize);
        temp -= CAMERA->getMapOffset();

        for (uint K = 0; K < tileSize * tileSize; ++K)
        {
            pix.setPosition(temp + Vector2df(K % tileSize, floor(K / tileSize)));
            pix.render();
        }
        debugPrint.setPosition(temp + Vector2df(tileSize / 2 - 18, tileSize / 2 - 18));
        debugPrint.print(StringUtility::intToString(GetHero()->m_collisionInfo.getCorrectionX(I).x));
    }
    pix.setColour(GREEN);
    pix.setAlpha(50);
    for (int I = 0; I < GetHero()->m_collisionInfo.getSizeY(); ++I)
    {
        Vector2df temp = GetHero()->m_collisionInfo.getTileY(I);
        temp *= Vector2df(tileSize,tileSize);
        temp -= CAMERA->getMapOffset();

        for (uint K = 0; K < tileSize * tileSize; ++K)
        {
            pix.setPosition(temp + Vector2df(K % tileSize, floor(K / tileSize)));
            pix.render();
        }
        debugPrint.setPosition(temp + Vector2df(tileSize / 2 + 5, tileSize / 2 + 5));
        debugPrint.print(StringUtility::intToString(GetHero()->m_collisionInfo.getCorrectionY(I).y));
    }

    debugPrint.setPosition(10,10);
    if (GetHero())
    {
        debugPrint.print(StringUtility::floatToString(GetHero()->getSpeed().x) + " | " + StringUtility::floatToString(GetHero()->getSpeed().y) + "\n");
        debugPrint.print(StringUtility::floatToString(GetHero()->getAcceleration().x) + " (" + StringUtility::floatToString(GetHero()->getMaxAcceleration().x) + ") | " +
                         StringUtility::floatToString(GetHero()->getAcceleration().y) + " (" + StringUtility::floatToString(GetHero()->getMaxAcceleration().y) + ")" + "\n");
        debugPrint.print(StringUtility::floatToString(GetHero()->getPosition().x + GetHero()->getPositionOffset().x) + " | " + StringUtility::floatToString(GetHero()->getPosition().y + GetHero()->getPositionOffset().y) + "\n");
        debugPrint.print("State: " + StringUtility::intToString(GetHero()->getState()) + "\n");
        debugPrint.print("Queue size: " + StringUtility::intToString(queue.size()) + "\n");
        debugPrint.print("Alive friendly units: " + StringUtility::intToString(friendlies.size()) + "\n");
        debugPrint.print("Alive enemy units: " + StringUtility::intToString(enemies.size()) + "\n");
        debugPrint.print("Triggers: " + StringUtility::intToString(triggers.size()) + "\n");
        debugPrint.print("---\n");
        debugPrint.print(debugText);
        debugText = "";
    }
#endif
}

void Level::pauseScreen() // pause render
{
    GFX::clearScreen();
    pauseBG.render();
    EFFECTS->render(); // blackened screen
    pauseText.print("Paused");
    pauseMenu.render();
}

void Level::update()
{
    CAMERA->update();

    EFFECTS->update();

    if (shouldCheckSpawn.w > 0 && shouldCheckSpawn.h > 0)
        CheckSpawn(shouldCheckSpawn);

    vector< BaseUnit* >::iterator I;

    // update units (physics, movement, etc.)
    if (GetHero())
        GetHero()->update();
    for (I = friendlies.begin(); I < friendlies.end(); ++I)
    {
        (*I)->action(GetHero());
        (*I)->update();
    }
    for (I = enemies.begin(); I < enemies.end(); ++I)
    {
        (*I)->action(GetHero());
        (*I)->update();
    }
    for (I = triggers.begin(); I < triggers.end(); ++I)
    {
        (*I)->update();
    }

    // setup vectors for unit range check (logic border)
    // if a unit/trigger is not in this rect, it will get removed from the game (and its status set to ssNotSpawned)
    SDL_Rect borders = CAMERA->getLogicTileRect();
    pair<Vector2df,Vector2df> rect = make_pair(Vector2df(borders.x * GetTileSize(),borders.y * GetTileSize()),Vector2df((borders.x + borders.w)  * GetTileSize(), (borders.y + borders.h)  * GetTileSize()));

    // collision checks (map and units)
    if (GetHero())
        GetHero()->checkCollision(NULL,NULL);
    for (I = friendlies.begin(); I < friendlies.end();)
    {
        ((StatefulUnit*)(*I))->checkCollision(GetEnemies(),GetHero());
        if ((*I)->readyToRemove() || not IsInRect((*I)->getPosition(),rect))
        {
            RemoveUnit(I,GetFriendlies());
        }
        else
        {
            ++I;
        }
    }
    for (I = enemies.begin(); I < enemies.end();)
    {
        ((StatefulUnit*)(*I))->checkCollision(GetFriendlies(),GetHero());
        if ((*I)->readyToRemove() || not IsInRect((*I)->getPosition(),rect))
        {
            RemoveUnit(I,GetEnemies());
        }
        else
        {
            ++I;
        }
    }
    for (I = triggers.begin(); I < triggers.end();)
    {
        PHYSICS->checkTriggerCollision((*I),GetHero());
        if ((*I)->readyToRemove()/* || not IsInRect((*I)->getPosition(),rect)*/)
        {
            RemoveUnit(I,GetTriggers());
        }
        else
        {
            ++I;
        }
    }

    // move the hero
    CAMERA->move(GetHero());

    // effects
    list<StatefulUnit *>::iterator iter;
    for (iter = effects.begin(); iter != effects.end();)
    {
        (*iter)->update();
        if ((*iter)->hasFinished())
        {
            delete (*iter);
            iter = effects.erase(iter);
        }
        else
        {
            ++iter;
        }
    }

    if (shouldResetSpawn)
        ResetSpawn();
}

void Level::logicPauseUpdate()
{
    // keep this call to update the pause timer (else the pause state will never end)
    WandorBaseState::logicPauseUpdate();

    EFFECTS->update();

    // update sprite effects
    list<StatefulUnit *>::iterator iter;
    for (iter = effects.begin(); iter != effects.end();)
    {
        (*iter)->update();
        if ((*iter)->hasFinished())
        {
            delete (*iter);
            iter = effects.erase(iter);
        }
        else
        {
            ++iter;
        }
    }
}

void Level::pauseUpdate()
{
    EFFECTS->update();
    pauseMenu.update();
}

void Level::onPause()
{
    pauseBG.update();
    MusicCache::GetMusicCache()->TogglePauseMusic();
    EFFECTS->colourOverlay(Colour(0,0,0,200));
    pauseMenu.setSelection(0);
}

void Level::onResume()
{
    MusicCache::GetMusicCache()->TogglePauseMusic();
    EFFECTS->removeOverlay();
}

/* Utility function - this sets us up for the next level */
void Level::GoToLevel(string levelFileName)
{
    // NOTE: This probably should be moved to the MyGame class
    if (levelFileName.find(".") != string::npos)
    {
        MyGame* game = MyGame::GetMyGame();
        game->SetLevelFileName(levelFileName);
        this->setNextState(STATE_LEVEL);
    }
}

/* Utility function - this sets us up for the next level */
void Level::GoToLevel(LevelData level)
{
    // NOTE: This probably should be moved to the MyGame class
    MyGame* game = MyGame::GetMyGame();
    game->SetLevelData(level);
    this->setNextState(STATE_LEVEL);
}

void Level::CheckSpawn(SDL_Rect& borders)
{
    pair<Vector2df,Vector2df> rect = make_pair(Vector2df(borders.x * GetTileSize(),borders.y * GetTileSize()),Vector2df((borders.x + borders.w)  * GetTileSize(), (borders.y + borders.h)  * GetTileSize()));
    list<SpawnUnitData*>::iterator I;
    for (I = queue.begin(); I != queue.end(); ++I)
    {
        if ((*I)->status == ssNotSpawned && IsInRect((*I)->spawnPos,rect))
        {
            SpawnUnit((*I));
        }
        else if (not VecCompare((*I)->spawnPos, rect.second)) // as the queue is ordered, we can stop here
            return;
    }
    borders.w = -1;
    borders.h = -1;
}

void Level::RegisterSpawnCheck(const SDL_Rect& borders)
{
    shouldCheckSpawn = borders;
}

void Level::ResetSpawn()
{
    vector< BaseUnit* >::iterator I;

    // remove all active units
    for (I = friendlies.begin(); I < friendlies.end();)
    {
        RemoveUnit(I,GetFriendlies());
    }
    for (I = enemies.begin(); I < enemies.end();)
    {
        RemoveUnit(I,GetEnemies());
    }
    /*for (I = triggers.begin(); I < triggers.end();)
    {
        RemoveUnit(I,GetTriggers());
    }*/

    // check queue
    list< SpawnUnitData* >::iterator K;
    for (K = queue.begin(); K != queue.end(); ++K)
    {
        switch ((*K)->status)
        {
            case ssSpawned: // this should not happen
                cout << "Error: Encountered spawned unit after clean-up!" << endl;
                (*K)->status = ssDead;
                break;
            case ssNotSpawned:
                break;
            case ssDead: // reset status if unit should respawn
                if ((*K)->respawn)
                    (*K)->status = ssNotSpawned;
                break;
        }
    }
    shouldResetSpawn = false;
}

void Level::RegisterSpawnReset()
{
    shouldResetSpawn = true;
}

void Level::SpawnUnit(SpawnUnitData* data)
{
    Factory *f = Factory::GetFactory();
    switch (data->type)
    {
        case utSprite:
            enemies.push_back(f->GetSprite(this,data->classname,data->spawnPos,data->spawnVel,data->parameter));
            enemies.at(enemies.size()-1)->assignSpawnData(data);
            break;
        case utGeneric:
            enemies.push_back(f->GetGenericSprite(this,data->classname,data->spawnPos,data->spawnVel,data->parameter));
            enemies.at(enemies.size()-1)->assignSpawnData(data);
            break;
        case utTrigger:
            triggers.push_back(f->GetTrigger(this,data->classname,data->spawnPos,data->spawnVel,data->parameter));
            triggers.at(triggers.size()-1)->assignSpawnData(data);
            break;
    }
    data->status = ssSpawned;
}

void Level::AddSpawnUnit(SpawnUnitData* data)
{
    list<SpawnUnitData*>::iterator I;
    for (I = queue.begin(); I != queue.end(); ++I)
    {
        if (VecCompare(data->spawnPos,(*I)->spawnPos))
        {
            queue.insert(I,data);
            return;
        }
    }
    queue.push_back(data);
}

void Level::RemoveUnit(vector< BaseUnit* >::iterator unit, vector< BaseUnit*>* vec)
{
    BaseUnit* current = (*unit);
    if (current->getSpawnData())
    {
        if (current->readyToRemove()) // unit is dead
            current->getSpawnData()->status = ssDead;
        else // unit walked off the screen
            current->getSpawnData()->status = ssNotSpawned;
    }
    delete current;
    vec->erase(unit);
}


void Level::AddUnit(BaseUnit *unit)
{
    enemies.push_back(unit);
};

void Level::AddTrigger(BaseUnit *trigger)
{
    triggers.push_back(trigger);
};

void Level::AddEffect(StatefulUnit *effect)
{
    effects.push_back(effect);
};

StatefulUnit* Level::AddEffect(string effectName, string stateName, const Vector2df p)
{
    StatefulUnit *effect = new StatefulUnit(p, Vector2df(0.0,0.0));
    effect->loadSprite(effectName);
    effect->setSpriteState(stateName);
    if (effect)
    {
        effect->setPosition(p);
        AddEffect(effect);
    }
    return effect;
};

void Level::RemoveEffect(StatefulUnit *effect)
{
    delete effect;
    effects.remove(effect);
};
